1 using System;
2 using System.Collections.Generic;
3
4 namespace ProceduralToolkit
5 {
6 /// <summary>
7 /// Array extensions
8 /// </summary>
9 public static class ArrayE
10 {
11 /// <summary>
12 /// Gets the next or the first node in the <see cref="LinkedList{T}"/>
13 /// </summary>
14 public static LinkedListNode<T> NextOrFirst<T>(this LinkedListNode<T> current)
15 {
16 return current.Next ?? current.List.First;
17 }
18
19 /// <summary>
20 /// Gets the previous or the last node in the <see cref="LinkedList{T}"/>
21 /// </summary>
22 public static LinkedListNode<T> PreviousOrLast<T>(this LinkedListNode<T> current)
23 {
24 return current.Previous ?? current.List.Last;
25 }
26
27 /// <summary>
28 /// Looped indexer getter, allows out of bounds indices
29 /// </summary>
30 public static T GetLooped<T>(this T[] array, int index)
31 {
32 if (index < 0)
33 {
34 index = index%array.Length + array.Length;
35 }
36 else if (index >= array.Length)
37 {
38 index %= array.Length;
39 }
40 return array[index];
41 }
42
43 /// <summary>
44 /// Looped indexer setter, allows out of bounds indices
45 /// </summary>
46 public static void SetLooped<T>(this T[] array, int index, T value)
47 {
48 if (index < 0)
49 {
50 index = index%array.Length + array.Length;
51 }
52 else if (index >= array.Length)
53 {
54 index %= array.Length;
55 }
56 array[index] = value;
57 }
58
59 /// <summary>
60 /// Looped indexer getter, allows out of bounds indices
61 /// </summary>
62 public static T GetLooped<T>(this List<T> array, int index)
63 {
64 if (index < 0)
65 {
66 index = index%array.Count + array.Count;
67 }
68 else if (index >= array.Count)
69 {
70 index %= array.Count;
71 }
72 return array[index];
73 }
74
75 /// <summary>
76 /// Looped indexer setter, allows out of bounds indices
77 /// </summary>
78 public static void SetLooped<T>(this List<T> array, int index, T value)
79 {
80 if (index < 0)
81 {
82 index = index%array.Count + array.Count;
83 }
84 else if (index >= array.Count)
85 {
86 index %= array.Count;
87 }
88 array[index] = value;
89 }
90
91 /// <summary>
92 /// Checks if <paramref name="vector"/> is within array bounds
93 /// </summary>
94 public static bool IsInBounds<T>(this T[,] array, Vector2Int vector)
95 {
96 return IsInBounds(array, vector.x, vector.y);
97 }
98
99 /// <summary>
100 /// Checks if <paramref name="x"/> and <paramref name="y"/> are within array bounds
101 /// </summary>
102 public static bool IsInBounds<T>(this T[,] array, int x, int y)
103 {
104 if (array == null)
105 {
106 throw new ArgumentNullException("array");
107 }
108 return x >= 0 && x < array.GetLength(0) && y >= 0 && y < array.GetLength(1);
109 }
110
111 /// <summary>
112 /// Visits all connected elements with the same value as start element
113 /// </summary>
114 /// <remarks>
115 /// https://en.wikipedia.org/wiki/Flood_fill
116 /// </remarks>
117 public static void FloodVisit<T>(this T[,] array, Vector2Int start, Action<int, int> visit)
118 {
119 FloodVisit(array, start.x, start.y, visit);
120 }
121
122 /// <summary>
123 /// Visits all connected elements with the same value as start element
124 /// </summary>
125 /// <remarks>
126 /// https://en.wikipedia.org/wiki/Flood_fill
127 /// </remarks>
128 public static void FloodVisit<T>(this T[,] array, int startX, int startY, Action<int, int> visit)
129 {
130 if (array == null)
131 {
132 throw new ArgumentNullException("array");
133 }
134 if (visit == null)
135 {
136 throw new ArgumentNullException("visit");
137 }
138 if (startX < 0 || startX >= array.GetLength(0))
139 {
140 throw new ArgumentOutOfRangeException("startX");
141 }
142 if (startY < 0 || startY >= array.GetLength(1))
143 {
144 throw new ArgumentOutOfRangeException("startY");
145 }
146
147 bool[,] processed = new bool[array.GetLength(0), array.GetLength(1)];
148 T value = array[startX, startY];
149
150 var queue = new Queue<Vector2Int>();
151 queue.Enqueue(new Vector2Int(startX, startY));
152 processed[startX, startY] = true;
153
154 while (queue.Count > 0)
155 {
156 Vector2Int cell = queue.Dequeue();
157
158 array.VisitVonNeumannNeighbours(cell.x, cell.y, true, (x, y) =>
159 {
160 if (array[x, y].Equals(value) && !processed[x, y])
161 {
162 queue.Enqueue(new Vector2Int(x, y));
163 processed[x, y] = true;
164 }
165 });
166
167 visit(cell.x, cell.y);
168 }
169 }
170
171 /// <summary>
172 /// Visits all connected elements with the same value as start element
173 /// </summary>
174 /// <remarks>
175 /// https://en.wikipedia.org/wiki/Flood_fill
176 /// </remarks>
177 public static void FloodVisit<T>(this T[,] array, Vector2Int start, Action<int, int, bool> visit)
178 {
179 FloodVisit(array, start.x, start.y, visit);
180 }
181
182 /// <summary>
183 /// Visits all connected elements with the same value as start element
184 /// </summary>
185 /// <remarks>
186 /// https://en.wikipedia.org/wiki/Flood_fill
187 /// </remarks>
188 public static void FloodVisit<T>(this T[,] array, int startX, int startY, Action<int, int, bool> visit)
189 {
190 if (array == null)
191 {
192 throw new ArgumentNullException("array");
193 }
194 if (visit == null)
195 {
196 throw new ArgumentNullException("visit");
197 }
198 if (startX < 0 || startX >= array.GetLength(0))
199 {
200 throw new ArgumentOutOfRangeException("startX");
201 }
202 if (startY < 0 || startY >= array.GetLength(1))
203 {
204 throw new ArgumentOutOfRangeException("startY");
205 }
206
207 bool[,] processed = new bool[array.GetLength(0), array.GetLength(1)];
208 T value = array[startX, startY];
209
210 var queue = new Queue<Vector2Int>();
211 queue.Enqueue(new Vector2Int(startX, startY));
212 processed[startX, startY] = true;
213
214 while (queue.Count > 0)
215 {
216 Vector2Int cell = queue.Dequeue();
217
218 bool isBorderCell = false;
219 array.VisitMooreNeighbours(cell.x, cell.y, false, (x, y) =>
220 {
221 if (array.IsInBounds(x, y))
222 {
223 if (array[x, y].Equals(value))
224 {
225 bool vonNeumannNeighbour = (x == cell.x || y == cell.y);
226 if (vonNeumannNeighbour && !processed[x, y])
227 {
228 queue.Enqueue(new Vector2Int(x, y));
229 processed[x, y] = true;
230 }
231 }
232 else
233 {
234 isBorderCell = true;
235 }
236 }
237 else
238 {
239 isBorderCell = true;
240 }
241 });
242
243 visit(cell.x, cell.y, isBorderCell);
244 }
245 }
246
247 /// <summary>
248 /// Visits four cells orthogonally surrounding a central cell
249 /// </summary>
250 /// <remarks>
251 /// https://en.wikipedia.org/wiki/Von_Neumann_neighborhood
252 /// </remarks>
253 public static void VisitVonNeumannNeighbours<T>(this T[,] array, Vector2Int center, bool checkArrayBounds,
254 Action<int, int> visit)
255 {
256 VisitVonNeumannNeighbours(array, center.x, center.y, checkArrayBounds, visit);
257 }
258
259 /// <summary>
260 /// Visits four cells orthogonally surrounding a central cell
261 /// </summary>
262 /// <remarks>
263 /// https://en.wikipedia.org/wiki/Von_Neumann_neighborhood
264 /// </remarks>
265 public static void VisitVonNeumannNeighbours<T>(this T[,] array, int x, int y, bool checkArrayBounds,
266 Action<int, int> visit)
267 {
268 if (array == null)
269 {
270 throw new ArgumentNullException("array");
271 }
272 if (visit == null)
273 {
274 throw new ArgumentNullException("visit");
275 }
276
277 if (checkArrayBounds)
278 {
279 if (x > 0)
280 {
281 visit(x - 1, y);
282 }
283 if (x + 1 < array.GetLength(0))
284 {
285 visit(x + 1, y);
286 }
287 if (y > 0)
288 {
289 visit(x, y - 1);
290 }
291 if (y + 1 < array.GetLength(1))
292 {
293 visit(x, y + 1);
294 }
295 }
296 else
297 {
298 visit(x - 1, y);
299 visit(x + 1, y);
300 visit(x, y - 1);
301 visit(x, y + 1);
302 }
303 }
304
305 /// <summary>
306 /// Visits eight cells surrounding a central cell
307 /// </summary>
308 /// <remarks>
309 /// https://en.wikipedia.org/wiki/Moore_neighborhood
310 /// </remarks>
311 public static void VisitMooreNeighbours<T>(this T[,] array, Vector2Int center, bool checkArrayBounds,
312 Action<int, int> visit)
313 {
314 VisitMooreNeighbours(array, center.x, center.y, checkArrayBounds, visit);
315 }
316
317 /// <summary>
318 /// Visits eight cells surrounding a central cell
319 /// </summary>
320 /// <remarks>
321 /// https://en.wikipedia.org/wiki/Moore_neighborhood
322 /// </remarks>
323 public static void VisitMooreNeighbours<T>(this T[,] array, int x, int y, bool checkArrayBounds,
324 Action<int, int> visit)
325 {
326 if (array == null)
327 {
328 throw new ArgumentNullException("array");
329 }
330 if (visit == null)
331 {
332 throw new ArgumentNullException("visit");
333 }
334
335 if (checkArrayBounds)
336 {
337 bool xGreaterThanZero = x > 0;
338 bool xLessThanWidth = x + 1 < array.GetLength(0);
339
340 bool yGreaterThanZero = y > 0;
341 bool yLessThanHeight = y + 1 < array.GetLength(1);
342
343 if (yGreaterThanZero)
344 {
345 if (xGreaterThanZero) visit(x - 1, y - 1);
346
347 visit(x, y - 1);
348
349 if (xLessThanWidth) visit(x + 1, y - 1);
350 }
351
352 if (xGreaterThanZero) visit(x - 1, y);
353 if (xLessThanWidth) visit(x + 1, y);
354
355 if (yLessThanHeight)
356 {
357 if (xGreaterThanZero) visit(x - 1, y + 1);
358
359 visit(x, y + 1);
360
361 if (xLessThanWidth) visit(x + 1, y + 1);
362 }
363 }
364 else
365 {
366 visit(x - 1, y - 1);
367 visit(x, y - 1);
368 visit(x + 1, y - 1);
369
370 visit(x - 1, y);
371 visit(x + 1, y);
372
373 visit(x - 1, y + 1);
374 visit(x, y + 1);
375 visit(x + 1, y + 1);
376 }
377 }
378 }
379 }